﻿#region summary

// Copyright (c)  MOKEYISH. All rights reserved.
// Licensed under the MIT License, See License.txt in the project root for license information.

#endregion

using System;

namespace NSQL.Expressions
{
    public class ArithmeticExpression : BaseExpression, IArithmeticExpression
    {
        public INumericExpression LeftExpression { get; set; }
        public INumericExpression RightExpression { get; set; }
        public ArithmeticOperator Operator { get; set; }

        public ArithmeticExpression()
        {
        }

        public ArithmeticExpression(IValueExpression leftExpression, IValueExpression rightExpression,
            ArithmeticOperator @operator)
        {
            LeftExpression = (INumericExpression) leftExpression;
            RightExpression = (INumericExpression) rightExpression;
            Operator = @operator;
        }

        public bool TryEval(out INumericExpression result)
        {
            var leftExpression = LeftExpression;
            var rightExpression = RightExpression;
            return TryEval(leftExpression, rightExpression, Operator, out result);
        }

        private bool TryEval(INumericExpression leftExpression, INumericExpression rightExpression,
            ArithmeticOperator op, out INumericExpression result)
        {
            if (leftExpression is ConstantExpression left && rightExpression is ConstantExpression right)
            {
                // both leftExpression and rightExpression are constant value.
                switch (op)
                {
                    case ArithmeticOperator.Plus:
                        result = (INumericExpression) (left + right);
                        return true;
                    case ArithmeticOperator.Minus:
                        result = (INumericExpression) (left - right);
                        return true;
                    case ArithmeticOperator.Multiply:
                        result = (INumericExpression) (left * right);
                        return true;
                    case ArithmeticOperator.Divide:
                        result = (INumericExpression) (left / right);
                        return true;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }


            if (rightExpression is IArithmeticExpression rightArithmetic)
            {
                // ReSharper disable once InvertIf
                if (!rightArithmetic.TryEval(out rightExpression))
                {
                    result = null;
                    return false;
                }
            }
            else
            {
                // ReSharper disable once InvertIf
                if (!(rightExpression is IConstantExpression))
                {
                    result = null;
                    return false;
                }
            }


            if (leftExpression is IArithmeticExpression leftArithmetic)
            {
                if (!leftArithmetic.TryEval(out leftExpression))
                {
                    if (leftArithmetic.Operator.Priority() == op.Priority())
                    {
                        if (TryEval(rightExpression, leftArithmetic.RightExpression, leftArithmetic.Operator,
                            out var result1))
                        {
                            if (op.Priority() == 0 && result1 is IConstantNumericExpression numericExpression)
                            {
                                // ReSharper disable once SwitchStatementMissingSomeCases
                                switch (op)
                                {
                                    case ArithmeticOperator.Plus:
                                        if (numericExpression.Sign == -1)
                                        {
                                            op = ArithmeticOperator.Minus;
                                            numericExpression.MakeOpposite();
                                        }

                                        break;
                                    case ArithmeticOperator.Minus:
                                        if (numericExpression.Sign == 1)
                                        {
                                            op = ArithmeticOperator.Plus;
                                            numericExpression.MakeOpposite();
                                        }

                                        break;
                                    default:
                                        throw new ArgumentOutOfRangeException(nameof(op), op, null);
                                }
                            }

                            result = new ArithmeticExpression(leftArithmetic.LeftExpression, result1, op);
                            return true;
                        }

                        if (TryEval(leftArithmetic.LeftExpression, rightExpression, op, out result1))
                        {
                            var leftOp = result1;
                            var rightOp = leftArithmetic.RightExpression;
                            op = leftArithmetic.Operator;
                            if (op.Priority() == 0 && result1 is IConstantNumericExpression numericExpression)
                            {
                                // ReSharper disable once SwitchStatementMissingSomeCases
                                switch (op)
                                {
                                    case ArithmeticOperator.Plus:
                                        if (numericExpression.Sign == -1)
                                        {
                                            op = ArithmeticOperator.Minus;
                                            numericExpression.MakeOpposite();
                                            var t = leftOp;
                                            leftOp = rightOp;
                                            rightOp = t;
                                        }

                                        break;
                                    case ArithmeticOperator.Minus:
                                        if (numericExpression.Sign == 1)
                                        {
                                            op = ArithmeticOperator.Plus;
                                            numericExpression.MakeOpposite();
                                            var t = leftOp;
                                            leftOp = rightOp;
                                            rightOp = t;
                                        }

                                        break;
                                    default:
                                        throw new ArgumentOutOfRangeException(nameof(op), op, null);
                                }
                            }

                            result = new ArithmeticExpression(leftOp, rightOp, op);
                            return true;
                        }
                    }

                    result = null;
                    return false;
                }
            }
            else
            {
                if (!(leftExpression is IConstantExpression))
                {
                    result = null;
                    return false;
                }
            }


            return TryEval(leftExpression, rightExpression, op, out result);
        }

        public override T Accept<T>(IExpressionVisitor<T> visitor) => visitor.Visit(this);

        public static ArithmeticExpression operator +(ArithmeticExpression left, IntegerExpression right)
        {
            return new ArithmeticExpression(left, right, ArithmeticOperator.Plus);
        }

        public static ArithmeticExpression operator -(ArithmeticExpression left, IntegerExpression right)
        {
            return new ArithmeticExpression(left, right, ArithmeticOperator.Minus);
        }

        public static ArithmeticOperator ParseOperator(string op)
        {
            switch (op)
            {
                case "+": return ArithmeticOperator.Plus;
                case "-": return ArithmeticOperator.Minus;
                case "*": return ArithmeticOperator.Multiply;
                case "/": return ArithmeticOperator.Divide;

                default:
                    throw new Exception("UnExpected arithmetic operator. expecting {+,-,*./}");
            }
        }

        public byte Sign { get; } = 0;
    }

    public enum ArithmeticOperator
    {
        /// <summary>
        /// operator: +
        /// </summary>
        Plus,

        /// <summary>
        /// operator: -
        /// </summary>
        Minus,

        /// <summary>
        /// operator: *
        /// </summary>
        Multiply,

        /// <summary>
        /// operator: /
        /// </summary>
        Divide
    }

    public static class ArithmeticOperatorExtensions
    {
        public static int Priority(this ArithmeticOperator arithmeticOperator)
        {
            switch (arithmeticOperator)
            {
                case ArithmeticOperator.Plus:
                case ArithmeticOperator.Minus:
                    return 0;
                case ArithmeticOperator.Multiply:
                case ArithmeticOperator.Divide:
                    return 1;
                default:
                    throw new ArgumentOutOfRangeException(nameof(arithmeticOperator), arithmeticOperator, null);
            }
        }
    }
}